<?php

namespace app\common\tools;

use app\admin\model\StorageChunkCache;
use app\admin\model\StorageChunksPosition;
use app\admin\model\StoragePosition;
use app\admin\model\StorageToken;
use think\db\Query;
use think\facade\App;
use think\facade\Cache;
use think\facade\Config;
use think\facade\Log;
use think\facade\Request;

class StorageTools
{
    public static function write($chunk_content, $chunk_md5, $to_storage = false)
    {

        Log::debug('write chunk:' . $chunk_md5);

        if (!$to_storage) {
            // 如果没有明确要求存储到存储节点上,那么存在本地

            $chunk_file_path = static::buildChunkCachePath($chunk_md5);

            if (file_exists($chunk_file_path)) {
                return true;
            }

            $host_key = Config::get('storage.host_key');
            $model_chunk_cache = StorageChunkCache::where('chunk_md5', $chunk_md5)
                ->where('host_key', $host_key)
                ->find();

            if (empty($model_chunk_cache)) {
                file_put_contents($chunk_file_path, $chunk_content);
                $model_chunk_cache = new StorageChunkCache();
                $model_chunk_cache->chunk_md5 = $chunk_md5;
                $model_chunk_cache->host_key = $host_key;
                $model_chunk_cache->host_domain = Request::domain();
                $model_chunk_cache->save();
            }

            return true;
        }


        $list_positoin = StoragePosition::where('status', 'enable')->where(function (Query $query) {
            $query->whereOr('size_limit', 0)
                ->whereOr('size_limit', '>', 'size_used');
        })->select();

        foreach ($list_positoin as $model_position) {
            $model_position->hamdist = gmp_hamdist(str_to_bin($chunk_md5), str_to_bin($model_position->md5));
        }

        $list_positoin_settled = $list_positoin->order('hamdist', 'asc')->order('weight', 'desc');

        $write_success = false;

        foreach ($list_positoin_settled as  $model_position) {
            try {

                $file_handler_tools = FileHandlerTools::buildInstance($model_position);

                $file_path = static::buildChunkPath($chunk_md5);

                $file_storage_path = $file_handler_tools->buildPath($file_path);

                $flysystem = $file_handler_tools->getFlysystem();

                if (!$flysystem->has($file_storage_path)) {
                    $flysystem->write($file_storage_path, $chunk_content);
                }

                $model_chunks_position = StorageChunksPosition::where('storage_position_id', $model_position->id)
                    ->where('chunk_md5', $chunk_md5)
                    ->find();

                if (empty($model_chunks_position)) {
                    $model_chunks_position = new StorageChunksPosition();
                    $model_chunks_position->storage_position_id = $model_position->id;
                    $model_chunks_position->chunk_md5 = $chunk_md5;
                    $model_chunks_position->save();
                }

                $write_success = true;
                break;
            } catch (\Throwable $th) {
                Log::debug('Write faild');
                Log::debug($th);
            }
        }

        if (!$write_success) {
            throw new \Exception("写入到所有位置均失败", 1);
        }

        return true;
    }

    public static function read($chunk_md5): string
    {

        if (empty($chunk_md5)) {
            throw new \Exception("chunk_md5 不能为空", 1);
        }

        Log::debug('Read chunk：' . $chunk_md5);

        $model_chunks_position = StorageChunksPosition::with(['storagePosition'])->where('chunk_md5', $chunk_md5)->find();

        if (empty($model_chunks_position)) {
            throw new \Exception("读取区块索引错误", 1);
        }

        $file_handler_tools = FileHandlerTools::buildInstance($model_chunks_position->storagePosition);

        $file_path = static::buildChunkPath($chunk_md5);

        $flysystem = $file_handler_tools->getFlysystem();

        $file_storage_path = $file_handler_tools->buildPath($file_path);

        $is_exits = $flysystem->has($file_storage_path);

        if (!$is_exits) {
            // 存储节点不存在，那么看看是否在host节点上

            $model_chunk_cache = StorageChunkCache::where('chunk_md5', $chunk_md5)->find();

            if (empty($model_chunk_cache)) {
                throw new \Exception("读取区块错误，区块不存在", 1);
            }

            if (Config::get('storage.host_key') == $model_chunk_cache->host_key) {
                // 如果就是当前站点
                $chunk_file_path = static::buildChunkCachePath($chunk_md5);
                if (!file_exists($chunk_file_path)) {
                    throw new \Exception("读取区块错误，区块不存在", 1);
                }

                $content = file_get_contents($chunk_file_path);

                return $content;
            } else {

                $inner_token = static::initInnerToken();

                try {

                    $content = file_get_contents($model_chunk_cache->domain . '/inner/Chunk/readCacheChunk?chunk_md5=' . $chunk_md5 . '&=token=' . $inner_token);
                    return $content;
                } catch (\Throwable $th) {
                    // throw new \Exception("读取区块错误，区块不存在", 1);

                    // 这种情况下，应该是chunk缓存在内网节点上，需要等待缓存上传到存储节点上
                    sleep(3);
                    return static::read($chunk_md5);
                }
            }
        }

        $content = $flysystem->read($file_storage_path);

        Log::debug('read chunk length:' . strlen($content));

        return $content;
    }

    public static function delete($chunk_md5)
    {
        $list_chunk_position = StorageChunksPosition::with(['storagePosition'])->where('chunk_md5', $chunk_md5)->select();

        foreach ($list_chunk_position as  $model_chunks_position) {

            $file_handler_tools = FileHandlerTools::buildInstance($model_chunks_position->storagePosition);

            $file_path = static::buildChunkPath($chunk_md5);

            $flysystem = $file_handler_tools->getFlysystem();

            $file_storage_path = $file_handler_tools->buildPath($file_path);

            $is_exits = $flysystem->has($file_storage_path);
            if (!$is_exits) {


                $model_chunk_cache = StorageChunkCache::where('chunk_md5', $chunk_md5)->find();

                if (empty($model_chunk_cache)) {

                    throw new \Exception("删除区块错误，区块不存在", 1);
                }

                if (Config::get('storage.host_key') == $model_chunk_cache->host_key) {
                    // 如果就是当前站点
                    $chunk_file_path = static::buildChunkCachePath($chunk_md5);
                    if (file_exists($chunk_file_path)) {
                        unlink($chunk_file_path);
                    }
                } else {

                    $inner_token = static::initInnerToken();

                    try {

                        file_get_contents($model_chunk_cache->domain . '/inner/Chunk/deleteCacheChunk?chunk_md5=' . $chunk_md5 . '&=token=' . $inner_token);
                    } catch (\Throwable $th) {
                    }
                }

                $model_chunk_cache->delete();
            } else {

                $flysystem->delete($file_storage_path);
            }
            $model_chunks_position->delete();
        }
    }

    public static function buildChunkPath($chunk_md5)
    {
        $prefix_path = 'chunks/';

        $file_path_prefix = substr($chunk_md5, 0, 2);

        $file_name = substr($chunk_md5, 2);

        $file_path = $prefix_path . $file_path_prefix . '/' . $file_name;

        return $file_path;
    }

    public static function buildChunkCachePath($chunk_md5)
    {
        $chunk_cache_path = Config::get('storage.chunk_cache_path');
        PathTools::intiDir($chunk_cache_path . 'temp');
        $chunk_file_path = $chunk_cache_path . '/' . $chunk_md5;

        return $chunk_file_path;
    }

    public static function initInnerToken()
    {
        $host_key = Config::get('storage.host_key');
        $model_token = StorageToken::where('host_key', $host_key)
            ->find();

        if (empty($model_token)) {
            $model_token = new StorageToken();
            $model_token->host_key = $host_key;
            $model_token->token = uniqid();
        }

        if ($model_token->expire_time < time()) {
            $model_token->expire_time = time() + 30;
        }

        $model_token->save();

        return $model_token->token;
    }
}
